replicas-cli 0.2.727 → 0.2.729

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +51 -244
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,7 +45,7 @@ Connect to a workspace via SSH.
45
45
  Open a workspace in VS Code via Remote SSH. Omit the argument to pick a workspace with the arrow keys and Enter, or pass a name or ID. Names with spaces work with or without quotes.
46
46
 
47
47
  ### Coding agents
48
- Use `replicas claude-auth` or `replicas codex-auth` to connect subscription credentials. Gemini CLI and Cursor use API keys configured in Coding Agents settings.
48
+ Use `replicas claude-auth` or `replicas codex-auth` to connect subscription credentials. Cursor uses API keys configured in Coding Agents settings.
49
49
 
50
50
  ## Configuration
51
51
 
package/dist/index.cjs CHANGED
@@ -7701,9 +7701,7 @@ var AGENT = {
7701
7701
  CLAUDE: "claude",
7702
7702
  CODEX: "codex",
7703
7703
  CURSOR: "cursor",
7704
- DEEPSEEK: "deepseek",
7705
7704
  FX: "fx",
7706
- GEMINI: "gemini",
7707
7705
  KIMI: "kimi",
7708
7706
  MUSE: "muse",
7709
7707
  OPENCODE: "opencode",
@@ -7715,7 +7713,6 @@ var VALID_RELAY_SUBAGENT_PROVIDERS = VALID_AGENT_PROVIDERS;
7715
7713
  var VALID_RELAY_BASE_PROVIDERS = [
7716
7714
  AGENT.CLAUDE,
7717
7715
  AGENT.CODEX,
7718
- AGENT.DEEPSEEK,
7719
7716
  AGENT.OPENCODE,
7720
7717
  AGENT.PI
7721
7718
  ];
@@ -7748,21 +7745,11 @@ var AGENT_PROVIDER_OPTIONS = [
7748
7745
  label: "Cursor",
7749
7746
  description: "Use Cursor for tasks"
7750
7747
  },
7751
- {
7752
- value: "deepseek",
7753
- label: "DeepSeek Harness",
7754
- description: "Use DeepSeek Harness for tasks"
7755
- },
7756
7748
  {
7757
7749
  value: "fx",
7758
7750
  label: "fx",
7759
7751
  description: "Use fx for tasks"
7760
7752
  },
7761
- {
7762
- value: "gemini",
7763
- label: "Gemini CLI",
7764
- description: "Use Gemini CLI for tasks"
7765
- },
7766
7753
  {
7767
7754
  value: "kimi",
7768
7755
  label: "Kimi Code",
@@ -7795,9 +7782,7 @@ var THINKING_LEVELS_BY_AGENT = {
7795
7782
  claude: [...STANDARD_THINKING_LEVELS, "ultracode"],
7796
7783
  codex: [...STANDARD_THINKING_LEVELS, "ultra"],
7797
7784
  cursor: STANDARD_THINKING_LEVELS,
7798
- deepseek: STANDARD_THINKING_LEVELS,
7799
7785
  fx: STANDARD_THINKING_LEVELS,
7800
- gemini: STANDARD_THINKING_LEVELS,
7801
7786
  kimi: STANDARD_THINKING_LEVELS,
7802
7787
  muse: VALID_THINKING_LEVELS.filter((level) => level !== "max" && level !== "ultracode"),
7803
7788
  opencode: STANDARD_THINKING_LEVELS,
@@ -7819,12 +7804,8 @@ function getProviderDisplayName(provider, variant = "short") {
7819
7804
  return "Codex";
7820
7805
  case "cursor":
7821
7806
  return "Cursor";
7822
- case "deepseek":
7823
- return variant === "long" ? "DeepSeek Harness" : "DeepSeek";
7824
7807
  case "fx":
7825
7808
  return "fx";
7826
- case "gemini":
7827
- return "Gemini";
7828
7809
  case "kimi":
7829
7810
  return variant === "long" ? "Kimi Code" : "Kimi";
7830
7811
  case "muse":
@@ -7880,7 +7861,6 @@ var CREDENTIAL_TYPE = {
7880
7861
  ASTER_API_KEY: "aster_api_key",
7881
7862
  OPENROUTER_API_KEY: "openrouter_api_key",
7882
7863
  AI_GATEWAY_API_KEY: "ai_gateway_api_key",
7883
- GEMINI_API_KEY: "gemini_api_key",
7884
7864
  MUSE_OAUTH: "muse_oauth",
7885
7865
  MUSE_API_KEY: "muse_api_key"
7886
7866
  };
@@ -7908,16 +7888,9 @@ var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
7908
7888
  [AGENT.CURSOR]: [
7909
7889
  { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.CURSOR_API_KEY }
7910
7890
  ],
7911
- [AGENT.DEEPSEEK]: [
7912
- { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY },
7913
- { method: CREDENTIAL_METHOD.OLLAMA, credentialType: CREDENTIAL_TYPE.OLLAMA }
7914
- ],
7915
7891
  [AGENT.FX]: [
7916
7892
  { method: CREDENTIAL_METHOD.AI_GATEWAY, credentialType: CREDENTIAL_TYPE.AI_GATEWAY_API_KEY }
7917
7893
  ],
7918
- [AGENT.GEMINI]: [
7919
- { method: CREDENTIAL_METHOD.API_KEY, credentialType: CREDENTIAL_TYPE.GEMINI_API_KEY }
7920
- ],
7921
7894
  [AGENT.KIMI]: [
7922
7895
  { method: CREDENTIAL_METHOD.AI_GATEWAY, credentialType: CREDENTIAL_TYPE.AI_GATEWAY_API_KEY },
7923
7896
  { method: CREDENTIAL_METHOD.OLLAMA, credentialType: CREDENTIAL_TYPE.OLLAMA }
@@ -22540,10 +22513,15 @@ var TEAM_PLAN = PLANS.team;
22540
22513
  var ENTERPRISE_PLAN = PLANS.enterprise;
22541
22514
 
22542
22515
  // ../shared/src/analytics/types.ts
22516
+ var credentialAccountSchema = external_exports.object({
22517
+ ownerName: external_exports.string().optional(),
22518
+ email: external_exports.string().optional()
22519
+ });
22543
22520
  var agentCredentialSnapshotSchema = external_exports.object({
22544
22521
  method: external_exports.enum(CREDENTIAL_METHOD),
22545
22522
  scope: external_exports.enum(CREDENTIAL_SCOPE),
22546
- revision: external_exports.string().optional()
22523
+ revision: external_exports.string().optional(),
22524
+ account: credentialAccountSchema.optional()
22547
22525
  });
22548
22526
  function isAuthMethod(value) {
22549
22527
  return typeof value === "string" && Object.values(CREDENTIAL_METHOD).some((method) => method === value);
@@ -22555,7 +22533,7 @@ function isCredentialScope(value) {
22555
22533
  // ../shared/src/workspace-credentials.ts
22556
22534
  var WORKSPACE_CREDENTIAL_PROVIDERS = ["claude", "codex"];
22557
22535
  var workspaceCredentialProviderSchema = external_exports.enum(WORKSPACE_CREDENTIAL_PROVIDERS);
22558
- var workspaceCredentialSelectionSchema = agentCredentialSnapshotSchema.omit({ revision: true });
22536
+ var workspaceCredentialSelectionSchema = agentCredentialSnapshotSchema.omit({ revision: true, account: true });
22559
22537
  var createWorkspaceCredentialSchema = workspaceCredentialSelectionSchema.extend({
22560
22538
  provider: external_exports.enum(VALID_CODING_AGENT_PROVIDERS)
22561
22539
  }).refine(({ provider, method }) => agentCredentialMethods(provider).includes(method), {
@@ -22645,7 +22623,6 @@ var OX_ALPHA_MODEL = "stealth/ox-alpha";
22645
22623
  var DEEPSEEK_V4_PRO_MODEL = "deepseek/deepseek-v4-pro-0813";
22646
22624
  var DEEPSEEK_V4_FLASH_MODEL = "deepseek/deepseek-v4-flash-0731";
22647
22625
  var DEFAULT_FX_MODEL = "zai/glm-5.2";
22648
- var DEFAULT_GEMINI_MODEL = "gemini-3.8-flash";
22649
22626
  var DEFAULT_KIMI_MODEL = "moonshotai/kimi-k2.6";
22650
22627
  var MUSE_SPARK_1_3_MODEL = "muse-spark-1.3";
22651
22628
  var DEFAULT_MUSE_MODEL = "muse-spark-1.2";
@@ -22666,12 +22643,6 @@ var OPENROUTER_MODELS = [
22666
22643
  "xiaomi/mimo-v2.5-pro",
22667
22644
  "meta/muse-spark-1.3"
22668
22645
  ];
22669
- function prioritizeDeepseekModels(models) {
22670
- return [
22671
- ...models.filter((model) => model.startsWith("deepseek/")),
22672
- ...models.filter((model) => !model.startsWith("deepseek/"))
22673
- ];
22674
- }
22675
22646
  function normalizeClaudeModel(model) {
22676
22647
  if (model === "opus" || model === CLAUDE_OPUS_1M_MODEL || model === LEGACY_CLAUDE_OPUS_1M_MODEL) {
22677
22648
  return DEFAULT_CLAUDE_MODEL;
@@ -22751,9 +22722,7 @@ var AGENT_MODELS = {
22751
22722
  CLAUDE_HAIKU_4_5_MODEL,
22752
22723
  "kimi-k2.5"
22753
22724
  ],
22754
- deepseek: prioritizeDeepseekModels(DEFAULT_OPENROUTER_MODELS),
22755
22725
  fx: [DEFAULT_FX_MODEL],
22756
- gemini: [DEFAULT_GEMINI_MODEL, "gemini-3.1-pro-preview"],
22757
22726
  kimi: [DEFAULT_KIMI_MODEL],
22758
22727
  muse: [MUSE_SPARK_1_3_MODEL, DEFAULT_MUSE_MODEL],
22759
22728
  opencode: [...DEFAULT_OPENROUTER_MODELS, ...ASTER_MODELS],
@@ -22811,8 +22780,6 @@ var MODEL_LABELS = {
22811
22780
  "xiaomi/mimo-v2.5-pro": "MiMo V2.5 Pro via OpenRouter",
22812
22781
  "moonshotai/kimi-k2.6": "Kimi K2.6 via OpenRouter",
22813
22782
  "moonshotai/kimi-k3": "Kimi K3 via OpenRouter",
22814
- "gemini-3-flash-preview": "Gemini 3 Flash Preview",
22815
- "gemini-3.1-pro-preview": "Gemini 3.1 Pro Preview",
22816
22783
  [MUSE_SPARK_1_3_MODEL]: "Muse Spark 1.3",
22817
22784
  [DEFAULT_MUSE_MODEL]: "Muse Spark 1.2",
22818
22785
  "meta/muse-spark-1.3": "Muse Spark 1.3 via OpenRouter",
@@ -22954,6 +22921,8 @@ var CHAT_INTERRUPTED_EVENT_TYPE = "replicas-interrupted";
22954
22921
  var AUTH_FALLBACK_EVENT_TYPE = "auth-fallback";
22955
22922
  function coerceAuthFallbackPayload(payload) {
22956
22923
  const { provider, reason, exhaustedMethod, exhaustedScope, candidateMethod, candidateScope, status, detail } = payload;
22924
+ const exhaustedAccount = credentialAccountSchema.safeParse(payload.exhaustedAccount).data;
22925
+ const candidateAccount = credentialAccountSchema.safeParse(payload.candidateAccount).data;
22957
22926
  if (typeof provider !== "string" || !isValidCodingAgentProvider(provider) || reason !== "rate_limit" && reason !== "out_of_credits" || !isAuthMethod(exhaustedMethod) || !isAuthMethod(candidateMethod) || !isCredentialScope(candidateScope) || status !== "switched" && status !== "failed") {
22958
22927
  return null;
22959
22928
  }
@@ -22962,8 +22931,10 @@ function coerceAuthFallbackPayload(payload) {
22962
22931
  reason,
22963
22932
  exhaustedMethod,
22964
22933
  ...isCredentialScope(exhaustedScope) ? { exhaustedScope } : {},
22934
+ ...exhaustedAccount ? { exhaustedAccount } : {},
22965
22935
  candidateMethod,
22966
22936
  candidateScope,
22937
+ ...candidateAccount ? { candidateAccount } : {},
22967
22938
  status,
22968
22939
  ...typeof detail === "string" && detail ? { detail } : {}
22969
22940
  };
@@ -23774,7 +23745,7 @@ gh pr review 123 --request-changes --body "Changes needed"
23774
23745
 
23775
23746
  ## Media in GitHub workflows
23776
23747
 
23777
- Media delivery is owned by \`references/MEDIA.md\`. Read its GitHub section before adding screenshots, recordings, diagrams, or HTML reports to a pull request, issue, review comment, or commit; it covers public Forge embeds for images and recording previews, organization-only HTML links, and the required **View in Replicas** link.
23748
+ Media delivery is owned by \`references/MEDIA.md\`. Read its GitHub section before adding screenshots, recordings, diagrams, or HTML reports to a pull request, issue, review comment, or commit; it covers the public links every image upload prints, recording previews, organization-only HTML links, and the required **View in Replicas** link.
23778
23749
  `;
23779
23750
  var GITHUB_ABILITY = {
23780
23751
  label: "GitHub",
@@ -23985,11 +23956,11 @@ If none of these apply, don't upload.
23985
23956
 
23986
23957
  \`replicas media upload\` is always the first delivery action. When the current task has an external destination, every other delivery step follows only after that command succeeds. For every file:
23987
23958
 
23988
- 1. First, run \`replicas media upload\` with the original file. Do not begin any destination upload until the command succeeds. This creates the canonical dashboard copy and prints a chat embed plus a per-file dashboard link. With public access, the printed Forge embed is the chat embed and is also safe to use on GitHub.
23959
+ 1. First, run \`replicas media upload\` with the original file. Do not begin any destination upload until the command succeeds. This creates the canonical dashboard copy and prints an embed plus a per-file dashboard link. Every image gets a public link, so its printed embed works in chat and on GitHub.
23989
23960
  2. Include the printed embed line in the Replicas chat reply, followed by **View in Canvas** using the matching per-file dashboard URL. HTML uses its printed page link instead of an image embed.
23990
- 3. Identify every destination in the task and use its inline representation from the table below. Slack, Linear, and GitLab require an upload of the same original bytes; GitHub uses the public Forge embed.
23961
+ 3. Identify every destination in the task and use its inline representation from the table below. Slack, Linear, and GitLab require an upload of the same original bytes; GitHub uses the image's printed public embed.
23991
23962
  4. Put the **View in Replicas** dashboard link next to the destination embed as a secondary link.
23992
- 5. Check each destination response. If its upload or Forge share fails, say so explicitly and keep the dashboard link; never claim that a dashboard link is an inline embed.
23963
+ 5. Check each destination response. If its upload or public link fails, say so explicitly and keep the dashboard link; never claim that a dashboard link is an inline embed.
23993
23964
 
23994
23965
  | Destination | Required file delivery | Inline result |
23995
23966
  |---|---|---|
@@ -23997,7 +23968,7 @@ If none of these apply, don't upload.
23997
23968
  | Slack | \`files.getUploadURLExternal\` \u2192 raw-byte POST \u2192 \`files.completeUploadExternal\` | Uploaded file in the channel or thread |
23998
23969
  | Linear | \`fileUpload\` mutation \u2192 presigned upload \u2192 issue/comment mutation | Returned \`assetUrl\` in Markdown |
23999
23970
  | GitLab | Project \`/uploads\` endpoint with the original bytes | Returned GitLab Markdown |
24000
- | GitHub | Replicas public Forge share for an image or recording poster | Printed Forge embed, linked to the recording when needed |
23971
+ | GitHub | Printed public embed for an image or recording poster | Printed embed, linked to the recording when needed |
24001
23972
 
24002
23973
  The dashboard link is the subtitle and jump-back path. It is never a substitute for the destination embed.
24003
23974
 
@@ -24027,8 +23998,8 @@ Names are normalized to hyphen-case (spaces become hyphens, e.g. \`Login page\`
24027
23998
 
24028
23999
  For each media file, the CLI prints an embed line and a dashboard link:
24029
24000
 
24030
- 1. Default and organization access print \`Replicas chat embed: ![filename](<api-url>)\`; this authenticated URL renders inline in Replicas chat **only** \u2014 see below.
24031
- 2. Public access (\`--access public\` or \`--share\`) prints \`![filename](<public-url>)\` as the Forge embed instead. This is the public, stable bearer URL to paste into GitHub when sharing is authorized.
24001
+ 1. Images always print \`![filename](<public-url>)\`. This public, stable link works in Replicas chat, GitHub, and anywhere else Markdown images render. No flag is needed.
24002
+ 2. Video and audio print \`Replicas chat embed: ![filename](<api-url>)\` by default; this authenticated URL renders inline in Replicas chat **only** \u2014 see below. \`--access public\` prints a public embed instead.
24032
24003
  3. \`View in Replicas: <deep-link>\` is a per-file dashboard URL of the form \`https://app.replicas.dev/workspaces/<workspace-id>?media=<media-id>\` that opens directly to that specific file.
24033
24004
 
24034
24005
  Match each "View in Replicas" line to the embed line directly above it \u2014 that's the deep link for that file.
@@ -24043,13 +24014,13 @@ replicas media upload recap.html --name "Visual recap" --access organization
24043
24014
 
24044
24015
  The printed \`Forge link (organization only)\` opens a dedicated page. Signed-in organization members go straight to it; signed-out members sign in and return to the page.
24045
24016
 
24046
- Use public access only when the media must render without Replicas authentication:
24017
+ Video and audio stay private unless they must play without Replicas sign-in; then pass \`--access public\` (alias \`--share\`):
24047
24018
 
24048
24019
  \`\`\`bash
24049
- replicas media upload screenshot.png --name "Login page" --access public
24020
+ replicas media upload demo.mp4 --name "Checkout demo" --access public
24050
24021
  \`\`\`
24051
24022
 
24052
- \`--share\` remains an alias for \`--access public\`. Public access prints the raw \`Forge embed\` Markdown instead of the authenticated chat-only embed for image, video, or audio media, backed by an opt-in bearer URL. Anyone with that unguessable URL can view the asset until it is rotated or revoked. HTML pages only support organization access. Use \`replicas media share <media-id>\` to rotate a public media URL and \`replicas media revoke <media-id>\` to revoke it.
24023
+ Public links are unguessable bearer URLs: anyone with the link can view the file until it is rotated or revoked. HTML pages only support organization access. Reuse the link printed at upload. \`replicas media share <media-id>\` issues a new link and breaks the old one everywhere it is embedded, and \`replicas media revoke <media-id>\` removes public access.
24053
24024
 
24054
24025
  Permanently delete an upload with \`replicas media delete <media-id>\`. The backend only permits deletion when the media belongs to the current workspace; media from another workspace returns not found.
24055
24026
 
@@ -24057,14 +24028,14 @@ Permanently delete an upload with \`replicas media delete <media-id>\`. The back
24057
24028
 
24058
24029
  | URL | What it is | Where it's allowed |
24059
24030
  |---|---|---|
24060
- | \`https://api.replicas.dev/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect to a presigned blob \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
24031
+ | \`https://api.replicas.dev/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect printed for private video, audio, and HTML \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
24061
24032
  | \`https://replicas.dev/organizations/<organization-id>/media/<media-id>\` | Organization-only page printed by \`--access organization\` | Pull requests and other places where every viewer belongs to the Replicas organization. |
24062
- | \`https://api.replicas.dev/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--access public\` or \`--share\` | The Replicas chat reply and GitHub embeds, for image, video, or audio media. |
24033
+ | \`https://api.replicas.dev/v1/media/public/<id>/<token>\` | Public bearer URL printed for every image, and for video or audio with \`--access public\` | The Replicas chat reply and GitHub embeds. |
24063
24034
  | \`https://app.replicas.dev/workspaces/<workspace-id>?media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external: Slack, GitHub, GitLab, Linear, PR bodies, comments, emails. |
24064
24035
 
24065
24036
  ### NEVER link the API redirect URL outside the Replicas chat reply
24066
24037
 
24067
- The \`api.replicas.dev/v1/media/<id>\` URL is **not public**. It's an authenticated redirect that only resolves to a presigned download when the Replicas chat session token is present. Anywhere else (Slack, Linear, GitHub, a browser tab the user opens directly, a customer copy-pasting from chat) it returns \`{"error":"Missing authorization token"}\` and renders as a broken image.
24038
+ The \`api.replicas.dev/v1/media/<id>\` URL is **not public**. Image uploads never print it; if you have one for an image from an older upload, run \`replicas media share <media-id>\` to get a public link. It's an authenticated redirect that only resolves to a presigned download when the Replicas chat session token is present. Anywhere else (Slack, Linear, GitHub, a browser tab the user opens directly, a customer copy-pasting from chat) it returns \`{"error":"Missing authorization token"}\` and renders as a broken image.
24068
24039
 
24069
24040
  This rule has no exceptions:
24070
24041
  - \u274C Never paste it in a Slack message \u2014 upload the actual bytes via the 3-step \`files.getUploadURLExternal\` \u2192 POST bytes \u2192 \`files.completeUploadExternal\` flow instead (see below).
@@ -24098,12 +24069,12 @@ After (or alongside) the embeds, include a \`[View in Canvas](<deep-link>)\` hyp
24098
24069
 
24099
24070
  After \`replicas media upload\` succeeds, external delivery has two required follow-up steps for every file:
24100
24071
 
24101
- 1. **Use the destination's inline representation:** upload the raw bytes to Slack, Linear, or GitLab; embed a public Forge share on GitHub.
24072
+ 1. **Use the destination's inline representation:** upload the raw bytes to Slack, Linear, or GitLab; embed the image's printed public link on GitHub.
24102
24073
  2. **Link the Replicas dashboard deep link** (\`?media=<id>\` form) so the user can jump straight to the file in the workspace.
24103
24074
 
24104
24075
  A dashboard link alone is not enough. An embed without the dashboard link is not enough. Do both.
24105
24076
 
24106
- Slack, Linear, and GitLab receive the original bytes through their native upload APIs. GitHub uses a Replicas public Forge share for screenshots and recording previews.
24077
+ Slack, Linear, and GitLab receive the original bytes through their native upload APIs. GitHub uses the printed public link for screenshots and recording previews.
24107
24078
 
24108
24079
  ### Slack \u2014 embed natively via the 3-step external file upload flow
24109
24080
 
@@ -24204,19 +24175,17 @@ The same flow works for issue bodies (use \`issueCreate\` / \`issueUpdate\` with
24204
24175
 
24205
24176
  Never paste the \`api.replicas.dev/v1/media/<id>\` URL into a Linear body or comment \u2014 it will render as a broken image to anyone outside your Replicas chat.
24206
24177
 
24207
- ### GitHub: inline images and recording previews via Forge
24208
-
24209
- Replicas authenticates GitHub with GitHub App tokens, which native GitHub CLI attachment uploads do not support. Do not use \`gh --attach\` for media. Use a public Forge share for images in PR descriptions, issues, and comments.
24178
+ ### GitHub: inline images and recording previews
24210
24179
 
24211
- Create a public share only for a public repository or after explicit consent to publish media from a private repository. If sharing is not authorized, keep the media private and include its **View in Replicas** link, explaining that inline GitHub images require a public share.
24180
+ Replicas authenticates GitHub with GitHub App tokens, which GitHub's attachment upload rejects, so \`gh --attach\` does not work. Use the public link every image upload prints, in public and private repositories alike, for PR descriptions, issues, and comments.
24212
24181
 
24213
- 1. Upload the image with public access. This creates both the Replicas dashboard copy and the Forge share:
24182
+ 1. Upload the image:
24214
24183
 
24215
24184
  \`\`\`bash
24216
- replicas media upload /tmp/screenshot.png --name "PR screenshot" --access public
24185
+ replicas media upload /tmp/screenshot.png --name "PR screenshot"
24217
24186
  \`\`\`
24218
24187
 
24219
- 2. Copy the printed \`![filename](<public-url>)\` Forge embed into the body file, followed by the matching **View in Replicas** link. Use the printed public URL verbatim; never use a local file path, the authenticated API URL, or an expiring storage URL.
24188
+ 2. Copy the printed \`![filename](<public-url>)\` line verbatim into the body file, followed by the matching **View in Replicas** link. Never use a local file path, a \`/v1/media/<id>\` URL, a Linear \`assetUrl\`, or an expiring storage URL.
24220
24189
  3. Create or update the PR using the body file:
24221
24190
 
24222
24191
  \`\`\`bash
@@ -24225,13 +24194,13 @@ gh pr edit 456 --body-file /tmp/pr-body.md
24225
24194
  gh pr comment 456 --body-file /tmp/comment.md
24226
24195
  \`\`\`
24227
24196
 
24228
- GitHub does not reliably render external videos as inline players. For a recording preview, upload the MP4 normally, generate a poster with \`ffmpeg -i /tmp/demo.mp4 -frames:v 1 /tmp/demo-poster.png\`, upload the poster with \`--access public\`, and link its Forge embed to the recording's **View in Replicas** URL:
24197
+ Videos stay private, and GitHub does not play external videos inline. Upload the MP4 normally, generate a poster with \`ffmpeg -i /tmp/demo.mp4 -frames:v 1 /tmp/demo-poster.png\`, upload the poster like any image, and link its printed embed to the recording's **View in Replicas** URL:
24229
24198
 
24230
24199
  \`\`\`markdown
24231
24200
  [![Demo recording](<public-poster-url>)](<recording-dashboard-url>)
24232
24201
  \`\`\`
24233
24202
 
24234
- Public forge shares are unauthenticated bearer capabilities. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering; never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
24203
+ Public links are unauthenticated bearer capabilities. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering; never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
24235
24204
 
24236
24205
  For a self-contained HTML report, upload it with \`--access organization\` and put the printed \`Forge link\` Markdown in the PR body or comment. GitHub cannot render the page inline, so use the normal link rather than image syntax. HTML pages cannot be shared publicly.
24237
24206
 
@@ -24241,7 +24210,7 @@ Do **not** commit screenshots to the repo, use placeholder URLs, expose the auth
24241
24210
 
24242
24211
  GitLab supports native project Markdown uploads. For a GitLab destination, always upload the media to Replicas first, then POST the same original file to the repository's \`/projects/:id/uploads\` API using the workspace GitLab credential. Insert the returned \`markdown\` value into the merge request body or comment and include the Replicas dashboard link. Image uploads render inline; MP4, MOV, and WebM uploads render as inline video players.
24243
24212
 
24244
- Do not use a Replicas public forge share for GitLab when the native project upload succeeds; native uploads preserve project access controls.
24213
+ Do not use a Replicas public link for GitLab when the native project upload succeeds; native uploads preserve project access controls.
24245
24214
 
24246
24215
  ## Recording defaults
24247
24216
 
@@ -24283,7 +24252,7 @@ replicas media upload chart-a.svg chart-b.svg --name "Latency chart" --name "Err
24283
24252
  - \`--kind <image|video|audio|html>\` \u2014 override auto-detection
24284
24253
  - \`--name <name>\`: required display name shown in the dashboard, one per file in order
24285
24254
  - \`--session-id <id>\` \u2014 associate the upload with a specific session
24286
- - \`--access organization|public\`: create an organization-only link, or a public bearer URL for non-HTML media
24255
+ - \`--access organization|public\`: for video, audio, and HTML, create an organization-only link or a public bearer URL (video and audio only). Images are always public
24287
24256
  - \`--share\`: alias for \`--access public\`; not supported for HTML
24288
24257
  `;
24289
24258
  var MEDIA_ABILITY = {
@@ -25819,165 +25788,6 @@ function parseCursorEvents(events) {
25819
25788
  return messages;
25820
25789
  }
25821
25790
 
25822
- // ../shared/src/display-message/parsers/deepseek-parser.ts
25823
- function parseArguments(value) {
25824
- if (typeof value !== "string") return void 0;
25825
- const parsed = safeJsonParse(value, value);
25826
- return isRecord(parsed) ? parsed : stringifyDisplayValue(parsed);
25827
- }
25828
- function getDeepseekAssistantMessageText(event, blockType) {
25829
- if (!isRecord(event) || event.type !== "assistant/message" || !isRecord(event.data) || !isRecord(event.data.message) || !Array.isArray(event.data.message.content)) return "";
25830
- return event.data.message.content.flatMap((block) => isRecord(block) && block.type === blockType && typeof block.text === "string" ? [block.text] : []).join("");
25831
- }
25832
- function parseDeepseekEvents(events) {
25833
- const messages = [];
25834
- for (const source of events) {
25835
- if (source.type === "event_msg" && source.payload.type === "user_message") {
25836
- if (typeof source.payload.message === "string" && source.payload.message.trim()) {
25837
- const messageId = source.payload[USER_MESSAGE_ID_PAYLOAD_KEY];
25838
- messages.push({
25839
- id: typeof messageId === "string" ? messageId : `deepseek-user-${source.timestamp}`,
25840
- type: "user",
25841
- content: source.payload.message,
25842
- timestamp: source.timestamp
25843
- });
25844
- }
25845
- continue;
25846
- }
25847
- if (source.type === "deepseek-error") {
25848
- messages.push({
25849
- id: `deepseek-error-${source.timestamp}`,
25850
- type: "error",
25851
- message: typeof source.payload.message === "string" ? source.payload.message : "DeepSeek Harness failed",
25852
- timestamp: source.timestamp
25853
- });
25854
- continue;
25855
- }
25856
- if (source.type === "deepseek-input-request") {
25857
- const requestId = typeof source.payload.requestId === "string" ? source.payload.requestId : `deepseek-input-${source.timestamp}`;
25858
- if (source.payload.kind === "approval") {
25859
- upsertDisplayMessage(messages, createCallDisplayMessage({
25860
- id: requestId,
25861
- server: "deepseek",
25862
- tool: typeof source.payload.toolName === "string" ? source.payload.toolName : "approval",
25863
- input: typeof source.payload.reason === "string" ? source.payload.reason : void 0,
25864
- status: "in_progress",
25865
- timestamp: source.timestamp,
25866
- inputRequest: {
25867
- requestId,
25868
- status: "pending",
25869
- options: [
25870
- { id: "deny", label: "Deny", variant: "secondary" },
25871
- { id: "allow", label: "Allow once", variant: "primary" }
25872
- ]
25873
- }
25874
- }));
25875
- } else {
25876
- const questions = Array.isArray(source.payload.questions) ? source.payload.questions : [];
25877
- upsertDisplayMessage(messages, createCallDisplayMessage({
25878
- id: requestId,
25879
- server: "deepseek",
25880
- tool: "ask_user",
25881
- status: "in_progress",
25882
- timestamp: source.timestamp,
25883
- inputRequest: {
25884
- requestId,
25885
- status: "pending",
25886
- questions: questions.flatMap((question) => {
25887
- if (!isRecord(question) || typeof question.id !== "string" || typeof question.question !== "string") return [];
25888
- const options = Array.isArray(question.options) ? question.options : [];
25889
- return [{
25890
- id: question.id,
25891
- prompt: question.question,
25892
- multiSelect: question.multiSelect === true,
25893
- options: options.flatMap((option) => isRecord(option) && typeof option.label === "string" ? [{ id: `${question.id}:${option.label}`, label: option.label, ...typeof option.description === "string" ? { description: option.description } : {} }] : [])
25894
- }];
25895
- })
25896
- }
25897
- }));
25898
- }
25899
- continue;
25900
- }
25901
- if (source.type === "deepseek-input-resolved") {
25902
- const requestId = typeof source.payload.requestId === "string" ? source.payload.requestId : "";
25903
- const existing = messages.find((message) => message.type === "tool_call" && message.inputRequest?.requestId === requestId);
25904
- if (existing?.type === "tool_call" && existing.inputRequest) {
25905
- existing.inputRequest.status = source.payload.status === "aborted" ? "aborted" : "resolved";
25906
- existing.status = source.payload.status === "aborted" ? "failed" : "completed";
25907
- }
25908
- continue;
25909
- }
25910
- if (source.type === "deepseek-jobs" && Array.isArray(source.payload.jobs)) {
25911
- for (const job of source.payload.jobs) {
25912
- if (!isRecord(job) || typeof job.id !== "string") continue;
25913
- const existing = messages.find((message) => message.type === "background_task" && message.taskId === job.id);
25914
- const status = normalizeBackgroundTaskStatus(job.status);
25915
- const next = {
25916
- id: existing?.id ?? `deepseek-job-${job.id}`,
25917
- type: "background_task",
25918
- taskId: job.id,
25919
- status,
25920
- description: typeof job.label === "string" ? job.label : void 0,
25921
- taskType: typeof job.kind === "string" ? job.kind : void 0,
25922
- error: status === "failed" && typeof job.detail === "string" ? job.detail : void 0,
25923
- timestamp: existing?.timestamp ?? source.timestamp
25924
- };
25925
- if (existing?.type === "background_task") Object.assign(existing, next);
25926
- else messages.push(next);
25927
- }
25928
- continue;
25929
- }
25930
- if (source.type !== "deepseek-session-event" || !isRecord(source.payload.event)) continue;
25931
- const event = source.payload.event;
25932
- const data = isRecord(event.data) ? event.data : {};
25933
- const seq = typeof event.seq === "number" ? event.seq : source.timestamp;
25934
- if (event.type === "assistant/message") {
25935
- const content = getDeepseekAssistantMessageText(event, "text");
25936
- const reasoning = getDeepseekAssistantMessageText(event, "reasoning");
25937
- if (reasoning.trim()) messages.push({ id: `deepseek-reasoning-${seq}`, type: "reasoning", content: reasoning, status: "completed", timestamp: source.timestamp });
25938
- if (content.trim()) messages.push({ id: `deepseek-assistant-${seq}`, type: "agent", content, timestamp: source.timestamp });
25939
- continue;
25940
- }
25941
- if (event.type === "tool/call") {
25942
- const callId = typeof data.callId === "string" ? data.callId : String(seq);
25943
- upsertDisplayMessage(messages, createCallDisplayMessage({
25944
- id: `deepseek-tool-${callId}`,
25945
- server: "deepseek",
25946
- tool: typeof data.name === "string" ? data.name : "tool",
25947
- input: parseArguments(data.arguments),
25948
- status: "in_progress",
25949
- timestamp: source.timestamp
25950
- }));
25951
- continue;
25952
- }
25953
- if (event.type === "tool/result") {
25954
- const message = isRecord(data.message) ? data.message : {};
25955
- const messageSource = isRecord(message.source) ? message.source : {};
25956
- const result = Array.isArray(message.content) && isRecord(message.content[0]) ? message.content[0] : {};
25957
- const callId = typeof messageSource.callId === "string" ? messageSource.callId : typeof data.callId === "string" ? data.callId : String(seq);
25958
- const existing = messages.find((message2) => message2.id === `deepseek-tool-${callId}` && message2.type === "tool_call");
25959
- if (existing?.type === "tool_call") {
25960
- existing.output = stringifyDisplayValue(result.content ?? data.result);
25961
- existing.status = result.isError === true || data.isError === true ? "failed" : "completed";
25962
- }
25963
- continue;
25964
- }
25965
- if (event.type === "todo/write" && Array.isArray(data.items)) {
25966
- messages.push({
25967
- id: `deepseek-todo-${seq}`,
25968
- type: "todo_list",
25969
- items: data.items.flatMap((item) => {
25970
- if (!isRecord(item) || typeof item.content !== "string") return [];
25971
- const itemStatus = item.status === "in_progress" || item.status === "completed" ? item.status : "pending";
25972
- return [{ text: item.content, completed: itemStatus === "completed", itemStatus }];
25973
- }),
25974
- timestamp: source.timestamp
25975
- });
25976
- }
25977
- }
25978
- return messages;
25979
- }
25980
-
25981
25791
  // ../shared/src/display-message/parsers/acp-parser.ts
25982
25792
  var FX_CONTEXT_DIAGNOSTIC = /^\[context\] skill catalog omitted .*?(?:\r?\n|$)/;
25983
25793
  function getAcpTextChunk(update, kind) {
@@ -27172,18 +26982,12 @@ function parseAgentEvents(events, agentType) {
27172
26982
  if (agentType === "cursor") {
27173
26983
  return parseCursorEvents(events);
27174
26984
  }
27175
- if (agentType === "deepseek") {
27176
- return parseDeepseekEvents(events);
27177
- }
27178
26985
  if (agentType === "fx") {
27179
26986
  return parseFxEvents(events);
27180
26987
  }
27181
26988
  if (agentType === "kimi") {
27182
26989
  return parseAcpEvents(events, "kimi");
27183
26990
  }
27184
- if (agentType === "gemini") {
27185
- return parseAcpEvents(events, "gemini");
27186
- }
27187
26991
  if (agentType === "muse") {
27188
26992
  return parseMuseEvents(events);
27189
26993
  }
@@ -27457,7 +27261,7 @@ var headlessFilesystemAgentResultSchema = external_exports.object({
27457
27261
  });
27458
27262
 
27459
27263
  // ../shared/src/cli-version.ts
27460
- var CLI_VERSION = "0.2.727";
27264
+ var CLI_VERSION = "0.2.729";
27461
27265
 
27462
27266
  // ../shared/src/version.ts
27463
27267
  function compareVersions(v1, v2) {
@@ -31536,38 +31340,41 @@ async function mediaUploadCommand(filePaths, options) {
31536
31340
  if (publicAccess && kinds.some((kind) => !isPublicMediaKind(kind))) {
31537
31341
  throw new Error("HTML pages only support organization access. Use --access organization.");
31538
31342
  }
31343
+ const shared = kinds.map((kind) => publicAccess || kind === MEDIA_KIND.IMAGE);
31539
31344
  const results = await Promise.allSettled(
31540
31345
  filePaths.map((filePath, i) => uploadOne(filePath, names[i], kinds[i], options))
31541
31346
  );
31542
- const shareResults = publicAccess ? await Promise.allSettled(
31347
+ const shareResults = await Promise.allSettled(
31543
31348
  results.map(
31544
- (result) => result.status === "fulfilled" ? shareMedia(result.value.media.id) : Promise.resolve(null)
31349
+ (result, i) => result.status === "fulfilled" && shared[i] ? shareMedia(result.value.media.id) : Promise.resolve(null)
31545
31350
  )
31546
- ) : [];
31351
+ );
31547
31352
  const config3 = readAgentConfig();
31548
31353
  const failures = [];
31549
31354
  for (let i = 0; i < results.length; i++) {
31550
31355
  const result = results[i];
31551
31356
  if (result.status === "fulfilled") {
31552
31357
  const { fileName, media } = result.value;
31553
- const shareResult = publicAccess ? shareResults[i] : void 0;
31358
+ const shareResult = shareResults[i];
31554
31359
  const publicUrl = shareResult?.status === "fulfilled" ? shareResult.value ?? void 0 : void 0;
31555
- if (publicAccess) {
31360
+ if (shared[i]) {
31556
31361
  if (!publicUrl) {
31557
- const error51 = shareResult?.status === "rejected" ? shareResult.reason : new Error("Missing public share URL");
31362
+ const reason = shareResult?.status === "rejected" ? shareResult.reason : new Error("Missing public share URL");
31558
31363
  failures.push({
31559
31364
  filePath: filePaths[i],
31560
31365
  operation: "share",
31561
- error: error51 instanceof Error ? error51 : new Error(String(error51))
31366
+ error: new Error(
31367
+ `${reason instanceof Error ? reason.message : String(reason)}. Uploaded as ${media.id}; retry with \`replicas media share ${media.id}\``
31368
+ )
31562
31369
  });
31563
31370
  }
31564
31371
  }
31565
31372
  if (media.kind === MEDIA_KIND.HTML) {
31566
31373
  console.log(`HTML page: [${fileName}](${MONOLITH_URL}/v1/media/${media.id})`);
31567
- } else if (!publicAccess || publicUrl) {
31374
+ } else if (!shared[i] || publicUrl) {
31568
31375
  console.log(formatMediaEmbed(fileName, media.id, publicUrl));
31569
31376
  }
31570
- if (options.access === "organization") {
31377
+ if (options.access === "organization" && !shared[i]) {
31571
31378
  console.log(
31572
31379
  `Forge link (organization only): [View ${options.name?.[i] ?? fileName}](${getOrganizationMediaUrl(media.organization_id, media.id)})`
31573
31380
  );
@@ -31608,7 +31415,7 @@ async function mediaListCommand(options) {
31608
31415
  const query = qs.toString() ? `?${qs.toString()}` : "";
31609
31416
  const { media } = await agentFetch(`/v1/engine/media${query}`);
31610
31417
  for (const m of media) {
31611
- console.log(`${MONOLITH_URL}/v1/media/${m.id} ${m.kind} ${m.size} ${m.created_at}`);
31418
+ console.log(`${m.id} ${m.kind} ${m.display_name ?? ""} ${m.size} ${m.created_at}`);
31612
31419
  }
31613
31420
  }
31614
31421
 
@@ -33181,7 +32988,7 @@ if (isAgentMode()) {
33181
32988
  "Required human-facing display name shown in the dashboard. Pass one --name per file, in order.",
33182
32989
  (value, previous) => [...previous, value],
33183
32990
  []
33184
- ).option("--access <access>", "Link access: organization (sign-in required) or public (non-HTML bearer URL)").option("--share", "Alias for --access public; not supported for HTML").action(async (files, options) => {
32991
+ ).option("--access <access>", "Link access for video, audio, and HTML: organization (sign-in required) or public (video and audio only). Images always get a public link").option("--share", "Alias for --access public; not supported for HTML").action(async (files, options) => {
33185
32992
  try {
33186
32993
  await mediaUploadCommand(files, options);
33187
32994
  } catch (error51) {
@@ -33193,7 +33000,7 @@ if (isAgentMode()) {
33193
33000
  process.exit(1);
33194
33001
  }
33195
33002
  });
33196
- media.command("share <mediaId>").description("Create or rotate a public forge URL for uploaded image, video, or audio media").action(async (mediaId) => {
33003
+ media.command("share <mediaId>").description("Rotate an image's public forge URL, or create one for video or audio media").action(async (mediaId) => {
33197
33004
  try {
33198
33005
  await mediaShareCommand(mediaId);
33199
33006
  } catch (error51) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.727",
3
+ "version": "0.2.729",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {